/* 请用主页dosboxtc编译. 编译及运行步骤:
   把本程序文件拖给dosboxtc\dosboxtc.exe
   Alt+C 选 Compile to OBJ
   Alt+C 选 Link EXE file
   Alt+R 选 Run
 */
#include <stdio.h>
#include <stdlib.h>
#include <bios.h>
#include <io.h>
#define PageUp   0x4900
#define PageDown 0x5100
#define Home     0x4700
#define End      0x4F00
#define Esc      0x011B

void char2hex(char xx, char s[]) /* 把8位数转化成16进制格式 */
{
	char t[] = "0123456789ABCDEF";
	s[0] = t[(xx >> 4) & 0x0F]; /* 高4位 */
	s[1] = t[xx & 0x0F];        /* 低4位 */
}

void long2hex(long offset, char s[]) /* 把32位数转化成16进制格式 */
{
	int i;
	char xx;
	for (i = 0; i < 4; i++)
	{
		offset = _lrotl(offset, 8); /* 循环左移8位, 把高8位移到低8位 */
		xx = offset & 0xFF;         /* 高24位置0, 保留低8位 */
		char2hex(xx, &s[i * 2]);    /* 把8位数转化成16进制格式 */
	}
}

void show_this_row(int row, long offset, char buf[], int bytes_on_row)
{	/* 显示当前一行:   行号       偏移    数组首地址      当前行字节数 */
	char far *vp = (char far *)0xB8000000;
	char s[] =
	    "00000000: xx xx xx xx|xx xx xx xx|xx xx xx xx|xx xx xx xx  ................";
	/*  |         |                                                |
	    |         |                                                |
	    00        10                                               59
	    上述3个数字是竖线对应位置元素的下标;
	    数组s的内容就是每行的输出格式:
	    其中左侧8个0表示当前偏移地址;
	    其中xx代表16进制格式的一个字节;
	    其中s[59]开始共16个点代表数组buf各个元素对应的ASCII字符。
	 */
	char pattern[] =
	    "00000000:            |           |           |                             ";
	int i;
	strcpy(s, pattern);
	long2hex(offset, s); /* 把32位偏移地址转化成16进制格式填入s左侧8个'0'处 */
	for (i = 0; i < bytes_on_row; i++) /* 把buf中各个字节转化成16进制格式填入s中的xx处 */
	{
		char2hex(buf[i], s + 10 + i * 3);
	}
	//-----------------------------
	for (i = 0; i < bytes_on_row; i++) /* 把buf中各个字节填入s右侧小数点处 */
	{
		s[59 + i] = buf[i];
	}
	// row = currRow
	vp = vp + row * 80 * 2;       /* 计算row行对应的视频地址 */
	for (i = 0; i < sizeof(s) - 1; i++) /* 输出s */
	{
		vp[i * 2] = s[i];
		if (i < 59 && s[i] == '|') /* 把竖线的前景色设为高亮度白色 */
			vp[i * 2 + 1] = 0x0F;
		else                       /* 其它字符的前景色设为白色 */
			vp[i * 2 + 1] = 0x07;
	}
}

void clear_this_page(void)       /* 清除屏幕0~15行 */
{
	char far *vp = (char far *)0xB8000000;
	int i, j;
	for (i = 0; i < 16; i++)      /* 汇编中可以使用rep stosw填入80*16个0020h */
	{
		for (j = 0; j < 80; j++)
		{
			*(vp + (i * 80 + j) * 2) = ' ';
			*(vp + (i * 80 + j) * 2 + 1) = 0;
		}
	}
}

void show_this_page(char buf[], long offset, int bytes_in_buf)
{	/* 显示当前页:   数组首地址       偏移        当前页字节数 */
	int i, rows, bytes_on_row;
	clear_this_page();
	rows = (bytes_in_buf + 15) / 16; /* 计算当前页的行数 */
	//i == currRow
	for (i = 0; i < rows; i++)
	{
		bytes_on_row = (i == rows - 1) ? (bytes_in_buf - i * 16) : 16; /* 当前行的字节数 */
		show_this_row(i, offset + i * 16, &buf[i * 16], bytes_on_row); /* 显示这一行 */
	}
}

main()
{
	char filename[100];
	char buf[256];
	int  handle, key, bytes_in_buf;
	long file_size, offset, n;
	puts("Please input filename:");
	gets(filename); /* 输入文件名; 汇编中可以调用int 21h的0Ah功能 */
	handle = _open(filename, 0); /* 打开文件, 返回句柄;
                                   汇编对应调用:
                                   mov ah, 3Dh
                                   mov al, 0; 对应_open()的第2个参数, 表示只读方式
                                   mov dx, offset filename
                                   int 21h
                                   mov handle, ax
                                 */
	if (handle == -1)
	{
		puts("Cannot open file!");
		exit(0); /* 汇编对应调用:
                  mov ah, 4Ch
                  mov al, 0; 对应exit()中的参数
                  int 21h
                */
	}
	file_size = lseek(handle, 0, 2);
	/* 移动文件指针;
	   汇编对应调用:
	   mov ah, 42h
	   mov al, 2; 对应lseek()的第3个参数,
	            ; 表示以EOF为参照点进行移动
	   mov bx, handle
	   mov cx, 0; \ 对应lseek()的第2个参数
	   mov dx, 0; /
	   int 21h
	   mov word ptr file_size[2], dx
	   mov word ptr file_size[0], ax
	 */
	offset = 0;
	do
	{
		//int  handle, key, bytes_in_buf;
		//long file_size, offset, n;
		n = file_size - offset;
		if (n >= 256)
			bytes_in_buf = 256;
		else
			bytes_in_buf = n;
		lseek(handle, offset, 0);
		/* 移动文件指针;
		   汇编对应调用:
		   mov ah, 42h
		   mov al, 0; 对应lseek()的第3个参数,
		            ; 表示以偏移0作为参照点进行移动
		   mov bx, handle
		   mov cx, word ptr offset[2]; \cx:dx合一起构成
		   mov dx, word ptr offset[0]; /32位值=offset
		   int 21h
		 */
		_read(handle, buf, bytes_in_buf);
		/* 读取文件中的bytes_in_buf个字节到buf中
		   汇编对应调用:
		   mov ah, 3Fh
		   mov bx, handle
		   mov cx, bytes_in_buf
		   mov dx, data
		   mov ds, dx
		   mov dx, offset buf
		   int 21h
		 */
		show_this_page(buf, offset, bytes_in_buf);
		key = bioskey(0);
		/* 键盘输入;
		   汇编对应调用:
		   mov ah, 0
		   int 16h
		 */
		switch (key)
		{
		case PageUp:
			offset = offset - 256;
			if (offset < 0)
				offset = 0;
			break;
		case PageDown:
			if (offset + 256 < file_size)
				offset = offset + 256;
			break;
		case Home:
			offset = 0;
			break;
		case End:
			offset = file_size - file_size % 256;
			if (offset == file_size)
				offset = file_size - 256;
			break;
		}
	} while (key != Esc);
	_close(handle);
	/* 关闭文件;
	  汇编对应调用:
	  mov ah, 3Eh
	  mov bx, handle
	  int 21h
	*/
}
